import numpy as np

class VRPDataGenerator:
    def __init__(self, min_nodes, max_nodes, capacity_range, demand_range, coord_range=(0, 1)):
        """
        VRPDataGenerator 类用于生成随机的容量限制车辆路径问题 (CVRP) 实例。

        Args:
            min_nodes (int): 最小客户点数量 (不含仓库)。
            max_nodes (int): 最大客户点数量 (不含仓库)。
            capacity_range (tuple): 车辆容量的范围 (min_cap, max_cap)。
                                   例如：(50, 100) 表示容量在 50 到 100 之间。
            demand_range (tuple): 客户需求范围 (min_demand, max_demand)。
                                  例如：(1, 10) 表示每个客户的需求在 1 到 10 之间。
            coord_range (tuple): 坐标范围 (min_coord, max_coord)。
                                 例如：(0, 1) 表示所有点在 [0, 1]x[0, 1] 的正方形区域内。
        """
        if not (isinstance(min_nodes, int) and isinstance(max_nodes, int) and min_nodes > 0 and min_nodes <= max_nodes):
            raise ValueError("min_nodes and max_nodes must be positive integers, and min_nodes <= max_nodes.")
        if not (isinstance(capacity_range, tuple) and len(capacity_range) == 2 and capacity_range[0] <= capacity_range[1]):
            raise ValueError("capacity_range must be a tuple (min_cap, max_cap) with min_cap <= max_cap.")
        if not (isinstance(demand_range, tuple) and len(demand_range) == 2 and demand_range[0] > 0 and demand_range[0] <= demand_range[1]):
            raise ValueError("demand_range must be a tuple (min_demand, max_demand) with min_demand > 0 and min_demand <= max_demand.")
        if not (isinstance(coord_range, tuple) and len(coord_range) == 2 and coord_range[0] <= coord_range[1]):
            raise ValueError("coord_range must be a tuple (min_coord, max_coord) with min_coord <= max_coord.")

        self.min_nodes = min_nodes
        self.max_nodes = max_nodes
        self.capacity_range = capacity_range
        self.demand_range = demand_range
        self.coord_range = coord_range

    def generate_instance(self):
        """
        生成一个随机 CVRP 实例。

        Returns:
            coords (np.ndarray): (num_nodes + 1, 2) 形状的坐标数组。
                                 coords[0] 是仓库的坐标。
                                 coords[1:] 是客户点的坐标。
            demands (np.ndarray): (num_nodes + 1,) 形状的需求数组。
                                  demands[0] 是仓库的需求（始终为 0）。
                                  demands[1:] 是客户点的需求。
            capacity (float): 单辆车辆的容量。
            num_nodes (int): 客户点数量（不含仓库）。
        """
        # 1. 随机确定客户点数量
        num_nodes = np.random.randint(self.min_nodes, self.max_nodes + 1)
        problem_size = num_nodes + 1  # 包含仓库在内的总点数

        # 2. 生成坐标
        # 仓库和客户点的坐标都在指定的范围内随机生成
        coords = np.random.uniform(self.coord_range[0], self.coord_range[1], size=(problem_size, 2))

        # 3. 生成需求
        # 客户点的需求在 demand_range 范围内随机生成
        # 仓库的需求固定为 0
        demands = np.random.randint(self.demand_range[0], self.demand_range[1] + 1, size=problem_size)
        demands[0] = 0  # 仓库没有需求

        # 4. 生成车辆容量
        capacity = np.random.uniform(self.capacity_range[0], self.capacity_range[1])

        # 5. 确保实例的可解性（重要！）
        # 如果总需求远超车辆容量，可能导致无法解决的实例
        # 这里的调整策略是经验性的，旨在防止生成“病态”的实例，
        # 例如，所有客户的总需求都无法被单辆车运送。
        total_customer_demand = np.sum(demands[1:])
        
        # 估算所需的最小车辆数，并确保容量足够覆盖
        # 例如，如果平均每辆车只能装载总需求的1/k，那么至少需要 k 辆车
        # 这里的 num_nodes / 5 + 1 是一个经验性的估计值，可以根据实际情况调整
        # 目标是避免总需求过大导致需要超多车辆，或单辆车容量过小导致无法服务任何客户
        min_required_capacity_per_vehicle = total_customer_demand / max(1, num_nodes / 5) # 确保分母不为0
        
        if capacity < min_required_capacity_per_vehicle:
            # 如果随机生成的容量太小，则将其调整到足以服务大部分客户的水平
            capacity = min_required_capacity_per_vehicle * 1.1 # 留一点余量
            
        # 另外一个简单的检查，确保任何单个客户的需求都不超过车辆容量
        if np.any(demands[1:] > capacity):
            # 如果有客户需求大于容量，则提高容量到满足最大客户需求
            capacity = np.max(demands[1:]) * 1.05 # 稍微大一点

        return coords, demands, capacity, num_nodes